package org.box.metadata.cli.util;

import java.lang.reflect.Field;
import java.util.LinkedHashSet;
import java.util.Set;

import org.box.metadata.cli.annotation.Command;
import org.box.metadata.cli.annotation.Option;
import org.box.metadata.cli.exception.InvalidConfigurationException;
import org.box.metadata.cli.exception.ParseException;

/**
 * A utility class
 * 
 * @author <a href="mailto:alexander.box@gmail.com">Alexander Korobka</a>
 *
 */
public class Util {

	/**
	 * Creates a Options list for the delegated parser
	 * @param cfgClazz
	 *            - the class of application's configuration
	 * 
	 * @return new instance of application's configuration
	 * 
	 * @throws InvalidConfigurationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration
	 */
	@SuppressWarnings("rawtypes")
	public static org.apache.commons.cli.Options createCLIOptions(Class... cfgClasses) throws InvalidConfigurationException {

		org.apache.commons.cli.Options options = new org.apache.commons.cli.Options();

		for (Class c : cfgClasses)
			for (Field f : c.getDeclaredFields()) {
				// add options
				if (f.isAnnotationPresent(Option.class)) {
					Option o = f.getAnnotation(Option.class);
	
					if (!o.hasArguments()
							&& f.getType().isInstance(new Boolean(false))) {
						throw new InvalidConfigurationException(
								String.format("Field '%s' must have Boolean type since it has no arguments",
										f.getName()));
					}
	
					options.addOption(new org.apache.commons.cli.Option(o
							.shortName(), o.fullName(), o.hasArguments(), o
							.description()));
				}
			}

		return options;
	}
	
	@SuppressWarnings("rawtypes")
	public static org.apache.commons.cli.Options createCLIOptions(boolean incCommonOptions, Command command, Class... cfgClasses) {
		Field f = getCommandField(command, cfgClasses);
		if (f == null)
			throw new IllegalStateException();
		
		org.apache.commons.cli.Options options = createCLIOptions(f.getType());
		if (incCommonOptions) {
			for (Object o : createCLIOptions(cfgClasses).getOptions())
				options.addOption((org.apache.commons.cli.Option) o);
		}
		return options;
	}


	/**
	 * Creates a list of commands from the given class
	 * 
	 * @param cfgClazz - the class to process
	 * 
	 * @return a list of commands in the given class
	 */
	@SuppressWarnings("rawtypes")
	public static Set<Command> createCLICommands(Class... cfgClasses) {
		
		Set<Command> commands = new LinkedHashSet<Command>();
		
		for (Class c : cfgClasses)
			for (Field f : c.getDeclaredFields()) {
				if (f.isAnnotationPresent(Command.class)) {
					commands.add(f.getAnnotation(Command.class));
				}
			}
		return commands;
	}
	
	/**
	 * Finds a Command's field in the given class by a command's name
	 * @param command - the command to find
	 * @param cfgClazz - the class to process
	 * 
	 * @return the Command's field from the given class
	 */
	@SuppressWarnings("rawtypes")
	public static Field getCommandField(Command command, Class... cfgClasses) {
		for (Class c : cfgClasses) {
			for (Field f : c.getDeclaredFields()) {
				if (f.isAnnotationPresent(Command.class) 
						&&  f.getAnnotation(Command.class).equals(command)) {
						return f;
				}
			}
		}
		return null;
	}

	
	/**
	 * Checks that <code>array</code> starts with <code>subArray</code>. 
	 * 
	 * @param array - an array to check in
	 * @param subArray - an array to check as sub array of <code>array</code>
	 * 
	 * @return true, if <code>subArray</code> starts with <code>array</code>
	 */
	public static boolean arrayStartsWith(String[] array, String[] subArray) {
		if (subArray.length > array.length) {
			return false;
		}

		for (int i = 0; i < subArray.length; i++) {
			if (!subArray[i].equalsIgnoreCase(array[i])) {
				return false;
			}
		}

		return true;
	}

	public static Object findEnumConst(Class<?> type, String key, String optionValue) throws ParseException {
		for (Object c : type.getEnumConstants()){
			Enum<?> e = (Enum<?>) c;
			if(e.name().equalsIgnoreCase(optionValue)){
				return c;
			};
		}
		
		throw new ParseException("Illegal property '%s' value: %s", key, optionValue);
	}
}
